package org.dreamwork.jasmine2.engine;

import org.dreamwork.util.StringUtil;
import org.dreamwork.util.VirtualFile;

import javax.servlet.*;
import javax.servlet.http.*;
import java.util.*;
import java.security.Principal;
import java.io.*;

/**
 * HttpServletRequest 包装器.
 * 
 * <p>包装 <code>HttpServletRequest</code> 接口，使其内置支持文件上传。
 */
public class JasmineRequest implements HttpServletRequest {
    public static final String KEY_MAX_UPLOAD_SIZE = "org.dreamwork.jasmine.max-upload-size";

    private HttpServletRequest request;
    private Hashtable<String, Object> map = new Hashtable<String, Object> ();
    private Map<String, Object> files = new HashMap<String, Object> ();

/*
    private boolean limitExceed = false;
    private String message;
*/

    private String boundary = null;

    public JasmineRequest (HttpServletRequest request) throws IOException {
        this.request = request;
//        parse ();
    }

    public HttpServletRequest getOrigRequest () {
        return request;
    }

    @SuppressWarnings ("unchecked")
    public void parse () /*throws IOException*/ {
        try {
            String contentType = request.getHeader ("content-type");
            int pos = contentType.lastIndexOf ("boundary=");
            if (pos != -1) boundary = contentType.substring (pos + "boundary=".length ());

            int contentLength = request.getIntHeader ("Content-Length");
            if (contentLength > 0) {
                Integer maxSize = (Integer) request.getSession ().getServletContext ().getAttribute (KEY_MAX_UPLOAD_SIZE);
                if (maxSize == null) maxSize = -1;
                if (maxSize > 0 && contentLength > maxSize) {
/*
                    limitExceed = true;
                    message = "Upload size exceed the limit: expect " + maxSize + ", receive " + contentLength;
                    return;
*/
                    LimitExceedException exception = new LimitExceedException ();
                    exception.setLimit (String.valueOf (maxSize));
                    exception.setReceive (String.valueOf (contentLength));
                    throw exception;
                }
            }

            if (boundary != null) {
                ServletInputStream in = request.getInputStream ();
                byte[] buff = new byte[1024], lastData = null;
                int len;
                String fieldName = null, fileName = null, type = null;
                OutputStream out = null;
                boolean start = false;
                String enc = request.getCharacterEncoding ();
                VirtualFile vfile = null;

                while ((len = in.readLine (buff, 0, buff.length)) != -1) {
                    String line = enc == null ? new String (buff, 0, len, "utf-8") : new String (buff, 0, len, enc);
                    if (JasmineEngine.debugMode) {
                        System.out.write (buff, 0, len);
                    }
                    if (line.startsWith ("Content-Disposition: form-data;")) { // field start
                        String[] a = line.split (";");
                        for (int i = 1; i < a.length; i++) {
                            String[] t = a [i].split ("=");
                            if (t[0].trim ().equals ("name")) {
                                fieldName = t[1].trim ();
                                fieldName = fieldName.substring (1, fieldName.length () - 1);
                            }
                            if (t[0].trim ().equals ("filename")) {
                                fileName = t[1].trim ();
                                fileName = fileName.substring (1, fileName.length () - 1);
                                len = in.readLine (buff, 0, buff.length);
                                if (JasmineEngine.debugMode) {
                                    System.out.write (buff, 0, len);
                                }
                                line = enc == null ? new String (buff, 0, len) : new String (buff, 0, len, enc);
                                type = line.substring ("Content-Type: ".length ()).trim ();
                            }
                        }
                        len = in.readLine (buff, 0, buff.length);
                        if (JasmineEngine.debugMode) {
                            System.out.write (buff, 0, len);
                        }
                    } else if (line.startsWith ("--" + boundary)) {
                        if (!start) continue;

                        if (lastData != null) {
                            int L = lastData.length;
                            if (L >= 2 && lastData [L - 2] == '\r' && lastData [L - 1] == '\n')
                                L -= 2;
                            out.write (lastData, 0, L);
                        }

                        if (fileName == null) {
                            ByteArrayOutputStream baos = (ByteArrayOutputStream) out;
                            String value = enc == null ?
                                new String (baos.toByteArray ()).trim () :
                                new String (baos.toByteArray (), enc).trim ();

//                            value = URLDecoder.decode (value, enc == null ? "utf-8" : enc);

                            if (!map.containsKey (fieldName))
                                map.put (fieldName, value);
                            else {
                                Object o = map.get (fieldName);
                                List<String> list;
                                if (o instanceof String) {
                                    list = new ArrayList<String> ();
                                    map.put (fieldName, list);
                                    list.add ((String) o);
                                } else {
                                    list = (List<String>) o;
                                }
                                list.add (value);
                            }
                        } else {
                            vfile.setClientPath (getPath (fileName));
                            vfile.setName (getName (fileName));
                            vfile.setType (type);
                            out.flush ();
                            out.close ();
//                            files.put (fieldName, vfile);
                            if (!StringUtil.isEmpty (vfile.getName ())) {
                                if (files.containsKey (fileName)) {
                                    Object obj = files.get (fileName);
                                    List<VirtualFile> list = null;
                                    if (obj instanceof VirtualFile) {
                                        list = new ArrayList<VirtualFile> ();
                                        files.put (fieldName, list);
                                    } else if (obj instanceof List) {
                                        list = (List<VirtualFile>) obj;
                                    }
                                    if (list != null)
                                        list.add (vfile);
                                } else {
                                    files.put (fieldName, vfile);
                                }
                            } else {
                                vfile.deleteTempFile ();
                            }
                        }
                        fieldName = null;
                        fileName = null;
                        type = null;
                        out = null;
                        lastData = null;
                    } else {
                        if (out == null) {
                            if (fileName == null) out = new ByteArrayOutputStream ();
                            else {
                                vfile = new VirtualFile (true);
                                out = vfile.getOutputStream ();
                            }
                        }
                        if (lastData != null) {
                            out.write (lastData);
                        }
                        lastData = new byte[len];
                        System.arraycopy (buff, 0, lastData, 0, len);
                        start = true;
                    }
                }
            }
        } catch (IOException ex) {
            throw new RuntimeException (ex);
        }
    }

    private String getPath (String fileName) {
        String t = fileName.replace ('\\', '/');
        int pos = t.lastIndexOf ('/');
        if (pos != -1) return fileName.substring (0, pos);

        return "";
    }

    private String getName (String fileName) {
        String t = fileName.replace ('\\', '/');
        int pos = t.lastIndexOf ('/');
        if (pos != -1) return fileName.substring (pos + 1);
        return fileName;
    }

    public String getEncoding () {
        String encHeader = request.getHeader ("accept-charset");
        int pos = encHeader.indexOf (",");
        if (pos >= 0) return encHeader.substring (0, pos);
        return encHeader;
    }

    public String getAuthType () {
        return request.getAuthType ();
    }

    public Cookie[] getCookies () {
        return request.getCookies ();
    }

    public long getDateHeader (String name) {
        return request.getDateHeader (name);
    }

    public String getHeader (String name) {
        return request.getHeader (name);
    }

    public Enumeration<String> getHeaders (String name) {
        return request.getHeaders (name);
    }

    public Enumeration<String> getHeaderNames () {
        return request.getHeaderNames ();
    }

    public int getIntHeader (String name) {
        return request.getIntHeader (name);
    }

    public String getMethod () {
        return request.getMethod ();
    }

    public String getPathInfo () {
        return request.getPathInfo ();
    }

    public String getPathTranslated () {
        return request.getPathTranslated ();
    }

    public String getContextPath () {
        return request.getContextPath ();
    }

    public String getQueryString () {
        return request.getQueryString ();
    }

    public String getRemoteUser () {
        return request.getRemoteUser ();
    }

    public boolean isUserInRole (String name) {
        return request.isUserInRole (name);
    }

    public Principal getUserPrincipal () {
        return request.getUserPrincipal ();
    }

    public String getRequestedSessionId () {
        return request.getRequestedSessionId ();
    }

    public String getRequestURI () {
        return request.getRequestURI ();
    }

    public StringBuffer getRequestURL () {
        return request.getRequestURL ();
    }

    public String getServletPath () {
        return request.getServletPath ();
    }

    public HttpSession getSession (boolean b) {
        return request.getSession (b);
    }

    public HttpSession getSession () {
        return request.getSession ();
    }

    public boolean isRequestedSessionIdValid () {
        return request.isRequestedSessionIdValid ();
    }

    public boolean isRequestedSessionIdFromCookie () {
        return request.isRequestedSessionIdFromCookie ();
    }

    public boolean isRequestedSessionIdFromURL () {
        return request.isRequestedSessionIdFromURL ();
    }

    /**
     * @deprecated
     * @return boolean
     */
    @SuppressWarnings ({"all"})
    public boolean isRequestedSessionIdFromUrl () {
        return request.isRequestedSessionIdFromUrl ();
    }

    @Override
    public boolean authenticate (HttpServletResponse response) throws IOException, ServletException {
        return request.authenticate (response);
    }

    @Override
    public void login (String s, String s1) throws ServletException {
        request.login (s, s1);
    }

    @Override
    public void logout () throws ServletException {
        request.logout ();
    }

    @Override
    public Collection<Part> getParts () throws IOException, IllegalStateException, ServletException {
        return request.getParts ();
    }

    @Override
    public Part getPart (String s) throws IOException, IllegalStateException, ServletException {
        return request.getPart (s);
    }

    public Object getAttribute (String name) {
        return request.getAttribute (name);
    }

    public Enumeration<String> getAttributeNames () {
        return request.getAttributeNames ();
    }

    public String getCharacterEncoding () {
        return request.getCharacterEncoding ();
    }

    public void setCharacterEncoding (String enc) throws UnsupportedEncodingException {
        request.setCharacterEncoding (enc);
    }

    public int getContentLength () {
        return request.getContentLength ();
    }

    public String getContentType () {
        return request.getContentType ();
    }

    public ServletInputStream getInputStream () throws IOException {
        return request.getInputStream ();
    }

    public String getParameter (String name) {
        if (map.size () == 0) parse ();
        Object o = map.get (name);
        if (o instanceof String)
            return (String) map.get (name);
        List list = (List) o;
        return (list != null && list.size () > 0) ? (String) list.get (0) : null;
    }

    public Enumeration<String> getParameterNames () {
        return map.keys ();
    }

    @SuppressWarnings ("unchecked")
    public String[] getParameterValues (String name) {
        if (map.size () == 0) parse ();
        Object o = map.get (name);
        if (o instanceof List) {
            List list = (List) o;
            String[] values = new String [list.size ()];
            return (String[]) list.toArray (values);
        } else if (o instanceof String) {
            return new String[] {(String) o};
        }
        return null;
    }

    public Map getParameterMap () {
        if (map.size () == 0) parse ();
        return map;
    }

    public String getProtocol () {
        return request.getProtocol ();
    }

    public String getScheme () {
        return request.getScheme ();
    }

    public String getServerName () {
        return request.getServerName ();
    }

    public int getServerPort () {
        return request.getServerPort ();
    }

    public BufferedReader getReader () throws IOException {
        return request.getReader ();
    }

    public String getRemoteAddr () {
        return request.getRemoteAddr ();
    }

    public String getRemoteHost () {
        return request.getRemoteHost ();
    }

    public void setAttribute (String name, Object value) {
        request.setAttribute (name, value);
    }

    public void removeAttribute (String name) {
        request.removeAttribute (name);
    }

    public Locale getLocale () {
        return request.getLocale ();
    }

    public Enumeration<Locale> getLocales () {
        return request.getLocales ();
    }

    public boolean isSecure () {
        return request.isSecure ();
    }

    public RequestDispatcher getRequestDispatcher (String path) {
        return request.getRequestDispatcher (path);
    }

    /**
     * @deprecated
     * @param path path
     * @return string
     */
    @SuppressWarnings ("all")
    public String getRealPath (String path) {
        return request.getRealPath (path);
    }

    public int getRemotePort () {
        return request.getRemotePort ();
    }

    public String getLocalName () {
        return request.getLocalName ();
    }

    public String getLocalAddr () {
        return request.getLocalAddr ();
    }

    public int getLocalPort () {
        return request.getLocalPort ();
    }

    @Override
    public ServletContext getServletContext () {
        return request.getServletContext ();
    }

    @Override
    public AsyncContext startAsync () {
        return request.startAsync ();
    }

    @Override
    public AsyncContext startAsync (ServletRequest request, ServletResponse response) {
        return this.request.startAsync (request, response);
    }

    @Override
    public boolean isAsyncStarted () {
        return request.isAsyncStarted ();
    }

    @Override
    public boolean isAsyncSupported () {
        return request.isAsyncSupported ();
    }

    @Override
    public AsyncContext getAsyncContext () {
        return request.getAsyncContext ();
    }

    @Override
    public DispatcherType getDispatcherType () {
        return request.getDispatcherType ();
    }

    public Iterator getVirtualFileNames () {
        if (files.size () == 0) parse ();
        return files.keySet ().iterator ();
    }

    @SuppressWarnings ("unchecked")
    public VirtualFile[] getVirtualFiles () {
        if (files.size () == 0) parse ();
        List<VirtualFile> list = new ArrayList<VirtualFile> ();
        for (Object o : files.values ()) {
            if (o instanceof VirtualFile) list.add ((VirtualFile) o);
            else list.addAll ((Collection<? extends VirtualFile>) o);
        }
        VirtualFile[] vfs = new VirtualFile[list.size ()];
        return list.toArray (vfs);
    }

    public VirtualFile getVirtualFile (String name) {
        if (files.size () == 0) parse ();

        Object obj = files.get (name);
        if (obj instanceof VirtualFile)
            return (VirtualFile) obj;
        else
            return (VirtualFile) ((List) obj).get (0);
    }

    public VirtualFile[] getVirtualFiles (String name) {
        if (files.size () == 0) parse ();
        Object obj = files.get (name);
        if (obj instanceof VirtualFile)
            return new VirtualFile[] { (VirtualFile)obj};
        else if (obj != null) {
            @SuppressWarnings ("unchecked")
            List<VirtualFile> list = (List<VirtualFile>) obj;
            VirtualFile[] vfs = new VirtualFile[list.size ()];
            return list.toArray (vfs);
        }

        return null;
    }

    public int getVirtualFileCount () {
        if (files.size () == 0) parse ();
        return files.size ();
    }
}
